package com.kj.misc.service;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.system.SystemProperties;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.Subscribe;
import com.ivan.toolkit.common.AtFile;
import com.kj.misc.domain.MiscStoreType;
import com.kj.misc.domain.MiscValue;
import com.kj.misc.domain.MiscValueTimestamp;

import lombok.Data;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;

@Component
@Log4j2
public class MiscStorage {
	
	@Getter
	private static AsyncEventBus eventBus = new AsyncEventBus("MiscStoreBus", Executors.newSingleThreadExecutor());
	
	public static void feed(String key, long time, long birth, double value) {
		StorageEvent e = new StorageEvent();
		e.setKey(key);
		e.setTime(time);
		e.setBirth(birth);
		e.setValue(value);
		eventBus.post(e);
	}
	
	@Autowired
	private MiscData data;
	
	private File configDir = null;
	
	private boolean started = false;
	
	private Map<String, MiscStoreType> keyMap = Maps.newHashMap(); 
	
	@PostConstruct
	public void initialize() {
		
		// initial configure directory
		File f = new File(SystemProperties.get("user.dir"));
		f = new File(f, ".misc");
		f = new File(f, "storage");
		if(!f.exists()) f.mkdirs();
		configDir = f;
		
		// register data event 
		data.getEventBus().register(this);
		
		// register inner event
		eventBus.register(this);
	}
	
	@PreDestroy
	public void terminate() {
		data.getEventBus().unregister(this);
		eventBus.unregister(this);
	}
	
	public void registerStore(MiscStoreType type, String key) {
		keyMap.put(key, type);
	}
	
	private boolean matchKeyType(String key, MiscStoreType type) {
		
		if(Strings.isEmpty(key) || type == null) return false;
		
		return type.equals(keyMap.get(key));
				
	}
	
	@Subscribe
	public void dispatchStorageEvent(StorageEvent event) {

		save(
				event.getKey(), 
				event.getTime(), 
				event.getBirth(), 
				event.getValue());
		
	}
	
	@Subscribe
	public void dispatchMiscEvent(MiscDataEvent event) {
		
		String key = event.getKey();
		
		if(matchKeyType(key, MiscStoreType.update)) {
			
			save(
					event.getKey(), 
					event.getNewValue().getBirth(), 
					event.getNewValue().getBirth(), 
					event.getNewValue().getValue());
			
		}
		
	}

	@Scheduled(cron="0/5 * *  * * ? ")
	public void scheduleStore() {
		
		if(!started) return;
		
		try {
			
			long now = System.currentTimeMillis() / 1000 * 1000;
		
			Map<String, MiscValue> values = data.snapshot();
			
			values.entrySet().stream().forEach(e->{
				
				if(matchKeyType(e.getKey(), MiscStoreType.period)) {
					
					save(
							e.getKey(),
							now, 
							e.getValue().getBirth(), 
							e.getValue().getValue());
					
				}
	
			});
		
		}catch(Exception e) {
			log.error("schedule exception. ", e);
		}
		
	}
	
	@Scheduled(cron="0 * * * * ? ")
	public void scheduleGuard() {
		
		log.info("Guarding storage start...");
		
		for(File f : configDir.listFiles()) {
			
			if(f.isFile()) {
				f.delete();
				continue;
			}
			
			AtFile.get(f).keepMaxFileCount(90);

		}
		
		log.info("Guarding storage end.");
	}
	
	public void start() {
		
		started = true;

		// clear all unused file
		Set<String> keys = keyMap.keySet();
		
		String[] dirs = configDir.list();
		for(String dir : dirs) {
			if(!keys.contains(dir)) {
				FileUtils.deleteQuietly(new File(configDir, dir));
				continue;
			}
			File f = new File(configDir, dir);
			if(f.isFile()) {
				FileUtils.deleteQuietly(f);
				continue;
			}
		}
		
	}
	
	public void stop() {
		started = false;
	}
	
	private void save(String key, long time, long timestamp, double value) {
		
		File path = new File(configDir, key);
		if(!path.exists()) path.mkdirs();
		
		String filename = timeFmt.format(time) + ".csv";
		File file = new File(path, filename);
		
		try(CSVPrinter printer = CSVFormat.DEFAULT.print(new FileWriter(file, true))){
			printer.printRecord(time, timestamp, value);
			printer.flush();
			printer.close();
		} catch(IOException e) {
			
		} 
	}
	
	public String[] listDataFiles(String key) {
		if(Strings.isEmpty(key))
			return new String[0];
		File path = new File(configDir, key);
		return path.list();
	}
	
	public List<MiscValueTimestamp> read(String key, String filename) {
		
		List<MiscValueTimestamp> result = Lists.newLinkedList();
		
		File path = new File(configDir, key);
		//if(!path.exists()) return result;
		
		//String filename = timeFmt.format(time) + ".csv";
		File file = new File(path, filename);
		if(!file.exists()) return result;
		
		return readFile(file);
		
	}
	
	public List<MiscValueTimestamp> readByDay(String key, long time) {
		
		List<MiscValueTimestamp> result = Lists.newLinkedList();
		
		if(Strings.isEmpty(key)) return result;
		
		File path = new File(configDir, key);
		if(!path.exists()) return result;
		
		String filename = timeFmt.format(time) + ".csv";
		File file = new File(path, filename);
		if(!file.exists()) return result;

		return readFile(file);
		
	}
	
	public static List<MiscValueTimestamp> readFile(File file) {
		
		List<MiscValueTimestamp> result = Lists.newLinkedList();
		
		if(!file.exists()) return result;
		
		try(FileReader fr = new FileReader(file)) {
			
			try(CSVParser parser = CSVFormat.DEFAULT.parse(fr)) {
				
				parser.forEach(csv->{
					
					try {
						long k = Long.parseLong(csv.get(0));
						long t = Long.parseLong(csv.get(1));
						double v = Double.parseDouble(csv.get(2));	
						result.add(new MiscValueTimestamp(k, t, v));
					} catch(Exception e) {
						
					}
					
				});
				
			} 
			
		} catch(Exception e) {
			
			
		}

		return result;
		
	}
	
	@Data
	static class StorageEvent {
		private String key;
		private long time;
		private long birth;
		private double value;
	}
	
	private final SimpleDateFormat timeFmt = 
			new SimpleDateFormat("yyyyMMdd");
	
}
